#===============================================================================
# Simple Pygame UI - window.py
# Copyright 2009 Michael Pratt
# Licensed exclusively as a personal code sample to 1st Playable Productions, LLC
#===============================================================================

import pygame; from pygame.locals import *

class Window(object):
	'''Class Window:
	Basic window class for the SUI system. Does not render and handles no events.
	Obeys all rules about visibility, z-ordering, etc, so is still useful as a simple 
	contiainer window.
	The Window inherits from the pygame.Rect class, so operations such as topleft,
	collidepoint, and all others should operate on the bounding box of this window.
	Widgets inheriting from this class should take this into account.
	Typically, all other SUI widgets should inherit from this class.
	'''
	def __init__(self, name, rect=None, z=0):
		'''Window.__init__:
		Initializes variables for an empty window.
		Arguments: String name, pygame.Rect rect = None, Integer z = 0
		'''
		if rect is None:
			self._rect = pygame.Rect(0, 0, 0, 0)
		elif isinstance(rect, list) or isinstance(rect, tuple):
			if len(rect) == 2:
				self._rect = pygame.Rect(rect[0], rect[1], 0, 0)
			elif len(rect) == 4:
				self._rect = pygame.Rect(rect[0], rect[1], rect[2], rect[3])
			else:
				raise Exception, "Invalid length rect passed to window initilization. Must be (x, y) or (x, y, w, h)."
		else:
			self._rect = rect
			
		self._childrenz = []
		
		self.name = name
		self.children = {}
		self.parent = None
		self.visible = True
		self.z = z
		self.lineage = [self]
		
		#Initialize the screenrect, the representation of the actual screen
		#space occupied by this widget.
		self.screenrect = pygame.Rect(0, 0, 0, 0)
		self._update_screen_rect()
		
#===============================================================================
# Default python object function overrides. 
# There should be no reason to override these again.
#===============================================================================
	def __del__(self):
		'''Override of standard deletion to clear references from children'''
		for item in self.children:
			item.parent = None
		self.children.clear()
	
	def __repr__(self):
		'''Returns string information about the widget'''
		return "<Widget '%s' - %s>" % (self.name, repr(self.__class__)[8:-2])
	
	def __cmp__(self, other):
		'''Window.__cmp__:
		Simple Z-based comparison. Allows the windows to be sorted.
		Arguments: Window other
		Returns: Positive if self.z < other.z, 0 if self.z == other.z, Negative if other.z > self.z
		'''
		if isinstance(other, Window):
			return cmp(other.z, self.z)
		else:
			raise Exception, "Cannot compare a Window to a %s" % type(other)
		
	def __len__(self):
		'''Window.__len__:
		Allow the window to respond to calls to len()
		Returns how many children the window has
		Arguments: Nothing
		Returns: Integer 
		'''
		return len(self.children)
		
	def __getitem__(self, name):
		'''Window.__getitem__:
		Allow the window to act like a dictionary with the [] operator
		Arguments: String name
		Returns: If exists, child with given name, otherwise None
		'''
		if name in self.children:
			return self.children[name]
		else:
			return None

#===============================================================================
# These functions are generally for internal use only. They should only
# be overridden for extremely special circumstances if you know what you
# are doing.
#===============================================================================
		
	def _update_z(self):
		'''Window._update_z:
		Updates the _childrenz list in z-order.
		Typically is called automatically.
		Arguments: Nothing
		Returns: Nothing
		'''
		self._childrenz = self.children.values()
		self._childrenz.sort()
		
	def _update_screen_rect(self):
		'''Window._update_screen_rect:
		Updates screenrect to represent the widget's current space on screen
		Typically is called automatically.
		Arguments: Nothing
		Returns: Nothing
		'''
		self.screenrect.topleft = (0, 0)
		for window in self.lineage:
			self.screenrect.left += window.left
			self.screenrect.top += window.top
			
	def _update(self):
		'''Window._update:
		Calls the overridden update(), then updates screen rect.
		Typically is called automatically.
		Arguments: Nothing
		Returns: Nothing
		'''
		self.update()
		self._update_screen_rect()
		
	def _paint(self, screen, offs_x, offs_y):
		'''Window._paint:
		Calls paint() to render the current window the the screen.
		Then calls _paint for child windows in z order.
		If this window is not visible, returns without doing anything.
		offs represents the current offset from the top-left of the screen as passed
		down in the heiarchy.
		Arguments: pygame.Surface screen, Integer offsx, Integer offsy
		Returns: Nothing
		'''
		if not self.visible:
			return
		offs_x += self.left
		offs_y += self.top
		
		#paint this window
		self.paint(screen, offs_x, offs_y)
		
		#paint children windows
		for kid in self._childrenz:
			kid._paint(screen, offs_x, offs_y)
			
	def _event(self, event):
		'''Window._event:
		Handles injected pygame-style events by passing them on to Window.handleEvent()
		If this window does not handle them,
		it will pass it on to children in z-order until one handles it.
		Arguments: pygame.event.Event event
		Returns: True if the event should be ignored by any further program execution.
		False if the event was not exclusively handled by the GUI.
		'''
		handled = self.handleEvent(event)
		for child in self._childrenz:
			handled = child._event(event)
			if handled:
				break
		return handled

#===============================================================================
# Public functions
# The functions in this section are general-purpose and should generally
# not need to be overridden.
#===============================================================================
	
	def add(self, *args):
		'''Window.add:
		Adds one or more windows as children.
		Will update z-order after finishing.
		Arguments: one or more Window objects: add(w1, [w2, [w3...]]) 
		Returns: Nothing
		'''
		for window in args:
			if not isinstance(window, Window):
				raise Exception, "Argument passed to add() is %s, not a Window." % type(window)
			if window.name in self.children:
				raise Exception, "Child %s already exists in window." % window.name
			
			#construct the lineage
			window.parent = self
			window.lineage = self.lineage + [window]
			
			self.children[window.name] = window
		self._update_z()
		
	def remove(self, name):
		'''Window.remove:
		Removes named window from this window.
		Should not destroy the window if there are other references to it.
		Arguments: String name 
		Returns: True if a window was removed, False if name does not exist.
		'''
		if name in self.children:
			del self.children[name]	
			self._update_z()
			return True
		else:
			return False
		
	def clear(self):
		'''Window.clear:
		Clears this window and breaks references to all children.
		Arguments: Nothing
		Returns: Nothing
		'''
		for item in self.children:
			item.parent = None
		self.children.clear()
		self._update_z()
		
	def isVisible(self):
		'''Window.isVisible:
		Arguments: Nothing
		Returns: False if this or any parent window is not visible, True otherwise
		'''
		for window in self.lineage:
			if not window.visible:
				return False
			
		return True
	
#===============================================================================
# The following functions are templates and should be overridden if widgets
# need their functionality.
#===============================================================================
	
	def paint(self, screen, offs_x, offs_y):
		'''Window.paint:
		Since the default window is empty, this function renders nothing.
		More advanced widgets need to override this with code to render the window to
		the screen. offs x and y represent the distance from the left and top of the screen
		that the current window resides at, and should be taken into account when blitting.
		Arguments: pygame.Surface screen, Integer offs_x, Integer offs_y
		Returns: Nothing.
		'''
		return
		
	def update(self):
		'''Window.update:
		Updates any neccesary window attributes to reflect changing position or size.
		Automatically called whenever position or size is changed through properties.
		May need to be called in special circumstances.
		Widgets need to override this function if they need to know when their position
		or size has changed.
		WARNING: Be careful if you modify position or size through window properties from
		update, as you may run into a loop. Try directly using self._rect instead.
		Arguments: Nothing
		Returns: Nothing
		'''
		pass
		
	def handleEvent(self, event):
		'''Window.handleEvent:
		The default window handles no events.
		If a more complicated widget needs to, they should override this function with
		code to detect and handle them.
		Arguments: pygame.event.Event event
		Returns: True if this widget handles this event and does not need anything else 
		to handle it. Otherwise, False.
		'''
		return False
	
#===============================================================================
# The following are wrappers for pygame rect functions.
#===============================================================================

	def move(self, x, y):
		'''Window.move:
		Moves the window by given interval.
		Arguments: Integer x, Integer y
		Returns: Nothing
		'''
		self._rect.move_ip(x, y)
	
	def inflate(self, x, y):
		'''Window.inflate:
		Grow window by given offset.
		Window remains centered around its current center.
		Arguments: Integer x, Integer y
		Returns: Nothing
		'''
		self._rect.inflate_ip(x, y)
    	
#===============================================================================
# The following are property getter/setters used to keep the UI system informed about
# changes to size, position, and z-order of windows.
#===============================================================================
	
	@property	
	def z(self):
		return self._z
	@z.setter
	def z(self, value):
		self._z = value
		self._update_z()
		
	@property
	def center(self):
		return self._rect.center
	@center.setter
	def center(self, value):
		self._rect.center = value
		self._update()
	
	@property
	def top(self):
		return self._rect.top
	@top.setter
	def top(self, value):
		self._rect.top = value
		self._update()
		
	@property
	def bottom(self):
		return self._rect.bottom
	@bottom.setter
	def bottom(self, value):
		self._rect.bottom = value
		self._update()
		
	@property
	def left(self):
		return self._rect.left
	@left.setter
	def left(self, value):
		self._rect.left = value
		self._update()
		
	@property
	def right(self):
		return self._rect.right
	@right.setter
	def right(self, value):
		self._rect.right = value
		self._update()
		
	@property
	def centerx(self):
		return self._rect.centerx
	@centerx.setter
	def centerx(self, value):
		self._rect.centerx = value
		self._update()		
		
	@property
	def centery(self):
		return self._rect.centery
	@centery.setter
	def centery(self, value):
		self._rect.centery = value
		self._update()	
		
	@property
	def topleft(self):
		return self._rect.topleft
	@topleft.setter
	def topleft(self, value):
		self._rect.topleft = value
		self._update()	
		
	@property
	def topright(self):
		return self._rect.topright
	@topright.setter
	def topright(self, value):
		self._rect.topright = value
		self._update()	
		
	@property
	def bottomleft(self):
		return self._rect.bottomleft
	@bottomleft.setter
	def bottomleft(self, value):
		self._rect.bottomleft = value
		self._update()	
		
	@property
	def bottomright(self):
		return self._rect.bottomright
	@bottomright.setter
	def bottomright(self, value):
		self._rect.bottomright = value
		self._update()	
		
	@property
	def midtop(self):
		return self._rect.midtop
	@midtop.setter
	def midtop(self, value):
		self._rect.midtop = value
		self._update()	
		
	@property
	def midbottom(self):
		return self._rect.midbottom
	@midbottom.setter
	def midbottom(self, value):
		self._rect.midbottom = value
		self._update()	
		
	@property
	def midleft(self):
		return self._rect.midleft
	@midleft.setter
	def midleft(self, value):
		self._rect.midleft = value
		self._update()	
		
	@property
	def midright(self):
		return self._rect.midright
	@midright.setter
	def midright(self, value):
		self._rect.midright = value
		self._update()	
		
	@property
	def size(self):
		return self._rect.size
	@size.setter
	def size(self, value):
		self._rect.size = value
		self._update()	
		
	@property
	def width(self):
		return self._rect.width
	@width.setter
	def width(self, value):
		self._rect.width = value
		self._update()	
		
	@property
	def height(self):
		return self._rect.height
	@height.setter
	def height(self, value):
		self._rect.height = value
		self._update()